package de.hu.wbi.histoner.train;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This is a post-processing step to generalize pattern to improve recall. For
 * example
 * <ul>
 * <li>spaces are replaced by the more general regular expression '\s'</li>
 * <li>Mentions of histone are replaced with [Hh]istone</li>
 * <li>...</li>
 * </ul>
 * 
 * 
 * @author philippe
 * 
 */
public class RegexOptimizer {

	private static String in = "data/pattern/pattern.annotated";
	private static String out = "data/pattern/pattern.optimized";

	public static void main(String[] args) throws IOException {

		List<String> expressions = new ArrayList<String>();	//List of all regular expressions
		Pattern histone = Pattern.compile("^<hnumber>");

		BufferedReader br = new BufferedReader(new FileReader(new File(in)));
		String line;
		while ((line = br.readLine()) != null) { // Read all pattern

			if (line.startsWith("#")) // Remove irrelevant pattern
				continue;

			Matcher m = histone.matcher(line);
			line = m.replaceAll("(?<foo>H|histone)[\\\\p{Punct}\\\\s]?<hnumber>"); //Replaces leading "<hnumber>..." pattern with "H|histone<hnumber>..." 
			expressions.add(line);
		}

		Set<String> tmp = new HashSet<String>();
		for (String string : expressions) {
			tmp.addAll(histoneRemover(string));		//Pattern containing "histone\\sH" get an additional pattern with "histone|H"
		}
		expressions.addAll(tmp);

		tmp = new HashSet<String>();
		for (String string : expressions) {			//Pattern with a H get two additional pattern (histone and histone H)
			tmp.addAll(histoneAdder(string));
		}
		expressions.addAll(tmp);

		expressions = whitespaceInducer(expressions); // Induce whitespaces between >< tags
		expressions = conjunctExchanger(expressions); // Replace conjunctions with more generic regex
		expressions = prepExchanger(expressions); // Replace prepositions with more generic regex
		expressions = spaceInducer(expressions);	//Allows space between mentions of "H|histone" and "<hnumber>"	
				
		//This is some rather long code for writing the modified pattern to the output file
		//The pattern are ordered by number of occurrence and  duplicates are removed
		Set<String> terms = new HashSet<String>(expressions);
		HashMap<Integer, List<String>> patterns = new HashMap<Integer, List<String>>();	//Maps between pattern-occurrence and pattern sets
		for (String string : terms) {
			if (string.startsWith("#"))
				continue;

			String array[] = string.split("\t");
			int key = Integer.parseInt(array[1]);

			List<String> pattern = new ArrayList<String>();
			if (patterns.containsKey(key))
				pattern.addAll(patterns.get(key));

			pattern.add(string);
			patterns.put(key, pattern);

		}

		BufferedWriter bw = new BufferedWriter(new FileWriter(new File(out)));
		ArrayList<Integer> al = new ArrayList<Integer>(patterns.keySet());
		Collections.sort(al);
		Collections.reverse(al);
		Set<String> written = new HashSet<String>();
		for (Integer key : al) {

			List<String> pattern = patterns.get(key);
			for(int i = 0; i<pattern.size(); i++){
				
				String [] array = pattern.get(i).split("\t");
				if(!written.contains(array[0])){
					bw.append(pattern.get(i));
					bw.append("\t");
					bw.append("pattern");
					bw.append(Integer.toString(key));
					bw.append("-");
					bw.append(Integer.toString(i));
					bw.append("\n");
					written.add(array[0]);
				}							
			}
		}

		bw.close();
		System.out.println("Optimization done; check " + out);
	}

	/**
	 * Adds for all mentions of 'histone H' two new pattern ('histone' and 'H')
	 * 
	 * @param s
	 * @return
	 */
	private static Set<String> histoneRemover(String s) {
		Set<String> regex = new TreeSet<String>();
		// regex.add(s);
		Pattern prep = Pattern.compile("histone\\sH", Pattern.CASE_INSENSITIVE);
		Matcher m = prep.matcher(s);

		Set<Span> spans = new TreeSet<Span>();
		while (m.find()) {
			int start = m.start();
			int end = m.end();
			spans.add(new Span(start, end));
		}

		for (Span span : spans) {
			StringBuilder sb = new StringBuilder(s);
			regex.add(sb.replace(span.getStart(), span.getEnd(),
					"(?<tmp>H|histone)").toString());
		}

		if (spans.size() == 0)
			regex.add(s);

		return regex;
	}

	/**
	 * searches for mentions of H\<hnumber\> and adds new expressions with
	 * 'histone H' and 'histone'
	 */
	private static Set<String> histoneAdder(String s) {
		Set<String> regex = new TreeSet<String>();
		regex.add(s);
		Pattern pattern = Pattern.compile("(?<![H|h]istone)\\s(H)<[h]number>"); // matches H<hnumber> without leading histone
		Matcher m = pattern.matcher(s);

		Set<Span> spans = new TreeSet<Span>();
		while (m.find()) {
			int start = m.start(1);
			int end = m.end(1);
			spans.add(new Span(start, end));
		}

		for (Span span : spans) {
			StringBuilder sb = new StringBuilder(s);
			regex.add(sb.replace(span.getStart(), span.getEnd(), "histone H")
					.toString());

			sb = new StringBuilder(s);
			regex.add(sb.replace(span.getStart(), span.getEnd(), "histone")
					.toString());
		}

		return regex;
	}

	/**
	 * Replace pattern containing a >\s< with a >\s-/<
	 * 
	 * @param regex
	 * @return
	 */
	private static List<String> whitespaceInducer(List<String> regex) {
		List<String> result = new ArrayList<String>();
		Pattern pattern = Pattern.compile(">\\s?<");

		for (String reg : regex) {
			Matcher m = pattern.matcher(reg);
			if (m.find())
				result.add(m.replaceAll(">[\\\\p{Punct}\\\\s]?<")); // Any punctuation or whitespace allowed
			else
				result.add(reg);
		}
		return result;
	}

	/**
	 * Replace token 'and' 'or' with both terms 'and|or'
	 */
	private static List<String> conjunctExchanger(List<String> set) {
		List<String> result = new ArrayList<String>();
		Pattern pattern = Pattern.compile("(\\s)(and|or)(\\s)");

		for (String reg : set) {
			Matcher m = pattern.matcher(reg);
			if (m.find())
				result.add(m.replaceAll("\\\\s(?:and|or)\\\\s"));

			else
				result.add(reg);
		}
		return result;
	}
		
	/**
    * People often write histone1 or H1, but we generalize the pattern to
    * \p\s<hnumber>
    * @param regex
    * @return
    */
   private static List<String> spaceInducer(List<String> regex) {
	List<String> result = new ArrayList<String>();
           Pattern pattern = Pattern.compile("(histone|H)(<hnumber>)"); //Matches e.g H<hnumber>

	for (String reg : regex) {
                   Matcher m = pattern.matcher(reg);

                   Set<Span> spans = new TreeSet<Span>();
                   while (m.find()) {
                       int start = m.start(2);
                           int end = m.end(2);
                           spans.add(new Span(start, end));
	        }

		StringBuilder sb = new StringBuilder(reg);
                   for (Span span : spans) {
                           sb = sb.replace(span.getStart(), span.getEnd(), "[\\p{Punct}\\s]?<hnumber>");//
                   }
                   result.add(sb.toString());

           }

           return result;
   }


	/**
	 * Replace whitespace tokens 'to' 'of' 'at' 'on' with 'to|of|at|on'
	 */
	private static List<String> prepExchanger(List<String> set) {
		List<String> result = new ArrayList<String>();
		Pattern pattern = Pattern.compile("(\\s)(to|of|at|on|in)(\\s)");

		for (String reg : set) {
			Matcher m = pattern.matcher(reg);
			if (m.find())
				result.add(m.replaceAll("\\\\s(?:to|of|at|on|in)\\\\s"));

			else
				result.add(reg);
		}
		return result;
	}
}
